import ScrollViewCell from "./ScrollViewCell";

// tslint:disable: max-classes-per-file

const { ccclass, property } = cc._decorator;

class WidgetModel {
    public node: cc.Node;
    public script: ScrollViewCell;
}

class CellModel {
    public pos?: cc.Vec2;
    public nodeType: string = "";
    public data?: any;
    public size?: cc.Size;
    public rect?: cc.Rect;
    public model?: WidgetModel;
}

const ScrollViewDirection = cc.Enum({
    TopToBottom: 1,
    BottomToTop: 2,
});

@ccclass
export default class ScrollView extends cc.ScrollView {

    @property
    public spacingX: number = 0; // 水平间隔

    @property
    public spacingY: number = 0; // 上下间隔

    @property
    public spacingTop: number = 0;

    @property
    public spacingBottom: number = 0; // 离下边缘距离

    @property
    public spacingLeft: number = 0;

    @property
    public maxXCellNum: number = 1; // 水平最大多少个cell

    @property({ type: cc.Enum(ScrollViewDirection) })
    public direction: any = 1;

    @property(cc.Node)
    public view: cc.Node = null;

    @property(cc.Node)
    public itemPrefabList: cc.Node[] = [];


    public cellList: CellModel[] = [];
    public cellModelMap: { [key: string]: cc.Node } = {};
    public viewRect: cc.Rect;
    public isInit: boolean = false;
    public nodePool: { [key: string]: WidgetModel[] } = {};
    public intervalTime = 0;
    public itemList: cc.Node[] = [];

    public start() {
        this.init();
    }

    public addItem(nodeType: string, data?: any): void {
        this.init();
        const cell: CellModel = new CellModel();
        cell.nodeType = nodeType;
        cell.data = data;
        cell.size = this.cellModelMap[nodeType].getContentSize();
        this.cellList.push(cell);
    }

    public refresh(): void {
        if (!this.isInit) {
            return;
        }
        this.computeCellPos();
        this.updateScrollView();
    }

    public refreshAllCellsData(): void {
        for (const cell of this.cellList) {
            if (cell.model) {
                cell.model.script.bindData(cell.data);
            }
        }
    }

    public removeItemByData(compareFunc: (element: any) => boolean): void {
        for (let index = 0; index < this.cellList.length; index++) {
            const cell: CellModel = this.cellList[index];
            if (compareFunc(cell.data)) {
                if (cell.model) {
                    this.putNode(cell.nodeType, cell.model);
                }
                this.cellList.splice(index, 1);

                this.refresh();
                break;
            }
        }
    }

    public clear(flag?: boolean): void {
        if (!this.isInit) {
            return;
        }
        for (const cell of this.cellList) {
            if (cell.model) {
                if (flag) {
                    cell.model.node.destroy();
                } else {
                    this.putNode(cell.nodeType, cell.model);
                }

            }
        }
        this.cellList = [];
        this.refresh();
    }

    public sort(func: (a: CellModel, b: CellModel) => number): void {
        this.cellList = this.cellList.sort(func);
        this.computeCellPos();
        this.updateScrollView();
    }

    public refreshItem<T>(func: (a: T) => boolean, updateData: T): void {

        for (const cell of this.cellList) {

            if (func(cell.data)) {
                cell.data = updateData;
                if (cell.model) {
                    cell.model.script.bindData(cell.data);
                }
            }
        }
    }

    private init(): void {
        if (this.isInit) {
            return;
        }

        this.view = this.node.getChildByName("View");
        this.node.on("scrolling", this.updateScrollView, this);
        this.initCellPrefabInfo();
        const viewPos = this.node.convertToWorldSpaceAR(this.view.position);

        this.viewRect = cc.rect(viewPos.x - this.view.width / 2, viewPos.y - this.view.height / 2, this.view.width * 2, this.view.height * 2);
        this.isInit = true;
    }

    private initCellPrefabInfo(): void {
        for (const element of this.itemPrefabList) {
            element.active = false;
            const cellBaseScr: ScrollViewCell = element.getComponent(ScrollViewCell);
            this.cellModelMap[cellBaseScr.nodeType] = element;
            this.nodePool[cellBaseScr.nodeType] = [];
        }
    }

    private computeCellPos(): void {
        const lineY: number = Math.ceil(this.cellList.length / this.maxXCellNum);
        let totalHeight: number = 0;
        for (let y = 0; y < lineY; y++) {
            const startIndex: number = y * this.maxXCellNum;
            let maxHeight: number = 0;
            for (let index = startIndex; index < startIndex + this.maxXCellNum; index++) {
                if (this.cellList[index]) {
                    const nodeSize: cc.Size = this.cellList[index].size;
                    const lineIndexX: number = index - y * this.maxXCellNum + 1;
                    const cellPosX: number = (lineIndexX - 1) * (nodeSize.width + this.spacingX) - this.content.width / 2 + this.spacingLeft + nodeSize.width / 2;
                    const cellPosY: number = -totalHeight - this.spacingTop - nodeSize.height / 2;
                    this.cellList[index].pos = cc.v2(cellPosX, cellPosY);
                    this.cellList[index].rect = cc.rect(cellPosX - nodeSize.width / 2, cellPosY - nodeSize.height / 2, nodeSize.width, nodeSize.height);
                    maxHeight = Math.max(nodeSize.height + this.spacingY, maxHeight);
                    const model: WidgetModel = this.cellList[index].model;
                    if (model && model.node) {
                        model.node.position = this.cellList[index].pos;
                    }
                }
            }
            totalHeight = totalHeight + maxHeight;
        }
        totalHeight += this.spacingBottom + this.spacingTop;
        const finalHeight: number = Math.max(this.node.height, totalHeight);
        this.content.height = finalHeight;
    }

    private updateScrollView(): void {
        for (const cell of this.cellList) {
            // 判断cell 是否在显示区域内 在就需要从池里获取一个cell
            const itemPos: cc.Vec2 = this.content.convertToWorldSpaceAR(cell.pos);

            cell.rect.x = itemPos.x - cell.rect.width / 2;
            cell.rect.y = itemPos.y - cell.rect.height / 2;
            // view 和item 是否有交集
            const viewPos = this.node.convertToWorldSpaceAR(this.view.position);
            if (!this.viewRect) {
                this.viewRect = cc.rect(viewPos.x - this.view.width / 2, viewPos.y - this.view.height / 2, this.view.width, this.view.height);
            } else {
                this.viewRect.x = viewPos.x - this.view.width;
                this.viewRect.y = viewPos.y - this.view.height;
            }
            const isInsert: boolean = cell.rect.intersects(this.viewRect);
            if (isInsert && cell.model === undefined) {
                // 有交集则需要显示item
                cell.model = this.getNode(cell.nodeType);
                cell.model.script.bindData(cell.data);
                cell.model.node.position = cell.pos;
            } else if (isInsert && cell.model) {
                if (cell.model.script.data !== cell.data) {
                    cell.model.script.bindData(cell.data);
                }
                cell.model.node.position = cell.pos;
            } else if (!isInsert && cell.model !== undefined) {
                // 没有交集则需要回收
                this.putNode(cell.nodeType, cell.model);
                cell.model = undefined;
            }
        }
    }

    private getNode(nodeType: string): WidgetModel {
        let model: WidgetModel;
        if (this.nodePool[nodeType].length > 0) {
            model = this.nodePool[nodeType].pop();
        } else {
            model = new WidgetModel();
            model.node = cc.instantiate(this.cellModelMap[nodeType]);
            model.node.active = true;
            model.script = model.node.getComponent(ScrollViewCell);
            this.content.addChild(model.node);
            this.itemList.push(model.node);
        }

        return model;
    }

    private putNode(nodeType: string, model: WidgetModel): void {
        model.script.data = undefined;

        model.node.position = cc.v2(cc.winSize.width * 3, cc.winSize.height * 3);
        model.node.x = cc.winSize.width * 3;
        model.node.y = cc.winSize.height * 3;
        model.script.inStack();
        this.nodePool[nodeType].push(model);
    }
}
